package net.cathackers.devel.scmp.chat.impl;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;
import org.dom4j.Attribute;
import org.dom4j.Document;

import net.cathackers.devel.scmp.auth.interfaces.AuthToken;
import net.cathackers.devel.scmp.auth.interfaces.UserID;
import net.cathackers.devel.scmp.auth.interfaces.UserProfile;
import net.cathackers.devel.scmp.chat.impl.xmpp.presence.PresenceInformation;
import net.cathackers.devel.scmp.chat.interfaces.ChatManager;
import net.cathackers.devel.scmp.chat.interfaces.ChatSession;
import net.cathackers.devel.scmp.chat.interfaces.XMPPConnection;
import net.cathackers.devel.scmp.config.impl.SCMPConstants;
import net.cathackers.devel.scmp.exceptions.NotAllowedException;
import net.cathackers.devel.scmp.factories.SCMPFactory;
import net.cathackers.devel.scmp.xmpp.Packet;
import net.cathackers.devel.scmp.xmpp.PresencePacket;

public class ClientChatSession implements ChatSession {
    private UserID userID_;
    private AuthToken authToken_;
    private PresenceInformation presence_;
    private XMPPConnection connection_;
    private int status_;
    private ChatManager manager_;
    private boolean authenticated_ = false;
    private Logger logger_;
    private boolean initial_;
 //   private Set<UserID> subscribers_;
    private boolean rosterRequested_ = false;

    ClientChatSession(XMPPConnection connection) {
	connection_ 	= connection;
	manager_ 	= SCMPFactory.getChatManager();
	connection_.setSessionID(manager_.generateSessionID());
	logger_ 	= SCMPFactory.getLogger(this);
//	subscribers_	= Collections.synchronizedSet(new HashSet<UserID>());
	presence_       = new PresenceInformation();
    }

    ClientChatSession() {
    }

    @Override
    public void setAuthToken(AuthToken authToken) {
	this.authToken_ = authToken;
	this.authenticated_ = true;
    }

    @Override
    public AuthToken getAuthToken() {
	return authToken_;
    }

    @Override
    public boolean isAuthenticated() {
	return authenticated_;
    }

    @Override
    public PresenceInformation getPresenceInformation() {
	return presence_;
    }

    @Override
    public void setUserID(UserID userID) {
	this.userID_ = userID;
    }

    @Override
    public UserID getUserID() {
	return userID_;
    }

    @Override
    public UserProfile getUserProfile() {
	return manager_.getUserProfile(userID_);
    }

    @Override
    public void requestAuthorization(UserID fromID) {
	// TODO Auto-generated method stub
    }

    @Override
    public void setPresence(PresenceInformation info) {
	this.presence_ = info;
    }

    @Override
    public void bye() {
	// TODO we will route presencePacket to PresenceComponent
	// in which we can handle it by removing this session from
	// onlineSessionCache
	// and broadcasting his presence by sending it to who are interested in
	// his presence and available
	logger_.info("Session " + this.getSessionID() + " For User " + this.getUserID().getCanonicalName() + " terminated!");
	connection_.close();
	PresencePacket presencePacket = new PresencePacket(
		PresencePacket.PresenceType.UNAVAILABLE);
	presencePacket.setFrom(getUserID());
	presencePacket.setBaseStreamID(this.getSessionID());
	manager_.route(presencePacket);

    }

    @Override
    public XMPPConnection getConnection() {
	return this.connection_;
    }

    @Override
    public void setConnection(XMPPConnection connection) {
	this.connection_ = connection;

    }

    @Override
    public String getPossibleStreamFeatures() {
	// Offer authenticate and registration only if TLS was not required or if required
        // then the connection is already secured
        if (connection_.getTLSPolicy() == XMPPConnection.TLSPolicy.required && !connection_.isTLS()) {
            return null;
        }

        StringBuilder sb = new StringBuilder(200);

        // Include Stream Compression Mechanism
        if (connection_.getCompressionPolicy() != XMPPConnection.CompressionPolicy.disabled &&
                !connection_.isCompressed()) {
            sb.append(
                    "<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>");
        }

        if (getAuthToken() == null) {
            // Advertise that the server supports Non-SASL Authentication
            sb.append("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
            // Advertise that the server supports In-Band Registration if we support it
            // TODO: make this optional (but it needs lots of work)
//                sb.append("<register xmlns=\"http://jabber.org/features/iq-register\"/>");
        } else {
            // If the session has been authenticated then offer resource binding
            // and session establishment
            sb.append("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
            sb.append("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
        }
        return sb.toString();
    }

    @Override
    public String getSessionID() {
	return connection_.getSessionID();
    }

    @Override
    public int getStatus() {
	return status_;
    }

    @Override
    public void setSessionID(String sessionID) {
	
	connection_.setSessionID(sessionID);

    }

    @Override
    public void setStatus(int status) {
	this.status_ = status;
    }

    @Override
    public String getAvailableStreamFeatures() {
	// Offer authenticate and registration only if TLS was not required or
	// if required
	// then the connection is already secured
	if (connection_.getTLSPolicy() == XMPPConnection.TLSPolicy.required
		&& !connection_.isTLS()) {
	    return null;
	}

	StringBuilder sb = new StringBuilder(200);

	// Include Stream Compression Mechanism
	if (connection_.getCompressionPolicy() != XMPPConnection.CompressionPolicy.disabled
		&& !connection_.isCompressed()) {
	    // We only support ZLib
	    sb
		    .append("<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>");
	}

	if (getAuthToken() == null) {
	    // Advertise that the server supports Non-SASL Authentication
	    sb.append("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
	    /*
	     * // Advertise that the server supports In-Band Registration if (we
	     * support in-band registration) {sb.append(
	     * "<register xmlns=\"http://jabber.org/features/iq-register\"/>");
	     * }
	     */
	} else {
	    // If the session has been authenticated then offer resource binding
	    // and session establishment
	    sb.append("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
	    sb
		    .append("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
	}
	return sb.toString();
    }

    public static ChatSession getInstance(String serverName, Document initDoc,
	    XMPPConnection connection) {
	Logger logger = SCMPFactory.getLogger(ClientChatSession.class);

	boolean isFlashClient = initDoc.getRootElement().getNamespacePrefix()
		.equals("flash");
	connection.setFlashClient(isFlashClient);

	// Default language is English ("en").
	String language = "en";
	// Default to a version of "0.0". Clients written before the XMPP 1.0
	// spec may
	// not report a version in which case "0.0" should be assumed (per
	// rfc3920
	// section 4.4.1).
	int majorVersion = 0;
	int minorVersion = 0;
	for (int i = 0; i < initDoc.getRootElement().attributeCount(); i++) {
	    Attribute attr = initDoc.getRootElement().attribute(i);
	    if ("lang".equals(attr.getName())) {
		language = attr.getValue();
	    }
	    if ("version".equals(attr.getName())) {
		try {
		    int[] version = decodeVersion(attr.getValue());
		    majorVersion = version[0];
		    minorVersion = version[1];
		} catch (Exception e) {
		    logger.error("Error decoding version:" + e.getMessage());
		}
	    }
	}

	// set the version to the highest one the server supports.
	if (majorVersion > MAJOR_VERSION) {
	    majorVersion = MAJOR_VERSION;
	    minorVersion = MINOR_VERSION;
	} else if (majorVersion == MAJOR_VERSION) {
	    // set the version to the highest one that the server
	    // supports.
	    if (minorVersion > MINOR_VERSION) {
		minorVersion = MINOR_VERSION;
	    }
	}
	connection.setLanaguage(language);
	connection.setXMPPVersion(majorVersion, minorVersion);

	// Indicate the TLS policy to use for this connection
	if (!connection.isTLS()) {
	    boolean hasCertificates = false;
	    try {
		hasCertificates = TLSConfig.getKeyStore().size() > 0;
	    } catch (Exception e) {
		logger.error("Error in KeyStore:" + e.getMessage());
	    }
	    // set the default policy to optional
	    // TODO this should come from the configuration file instead
	    XMPPConnection.TLSPolicy tlsPolicy = SCMPFactory.getXMPPConfiguration().getTLSPolicy();
	    if (XMPPConnection.TLSPolicy.required == tlsPolicy
		    && !hasCertificates) {
		logger
			.error("Client session rejected. TLS is required but no certificates "
				+ "were created.");
		return null;
	    }
	    // Set default TLS policy
	    connection.setTLSPolicy(hasCertificates ? tlsPolicy
		    : XMPPConnection.TLSPolicy.disabled);
	} else {
	    // Set default TLS policy
	    connection.setTLSPolicy(XMPPConnection.TLSPolicy.disabled);
	}

	// set the default policy to optional
	// TODO this should come from the configuration file instead
	connection
		.setCompressionPolicy(XMPPConnection.CompressionPolicy.optional);

	// Create a ClientSession for this user.
	ClientChatSession session = new ClientChatSession(connection);

	// Build the start packet response
	StringBuilder sb = new StringBuilder(200);
	sb.append("<?xml version='1.0' encoding='");
	sb.append(SCMPConstants._ENCODING_);
	sb.append("'?>");
	if (isFlashClient) {
	    sb
		    .append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" ");
	} else {
	    sb.append("<stream:stream ");
	}
	sb
		.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\" from=\"");
	sb.append(serverName);
	sb.append("\" id=\"");
	sb.append(session.getSessionID());
	sb.append("\" xml:lang=\"");
	sb.append(language);
	// Don't include version info if the version is 0.0.
	if (majorVersion != 0) {
	    sb.append("\" version=\"");
	    sb.append(majorVersion).append(".").append(minorVersion);
	}
	sb.append("\">");
	connection.deliverString(sb.toString());

	// If this is a "Jabber" connection, the session is now initialized and
	// we can
	// return to allow normal packet parsing.
	if (majorVersion == 0) {
	    return session;
	}
	// Otherwise, this is at least XMPP 1.0 so we need to announce stream
	// features.

	sb = new StringBuilder(490);
	sb.append("<stream:features>");
	if (connection.getTLSPolicy() != XMPPConnection.TLSPolicy.disabled) {
	    sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
	    if (connection.getTLSPolicy() == XMPPConnection.TLSPolicy.required) {
		sb.append("<required/>");
	    }
	    sb.append("</starttls>");
	}
	// Include available SASL Mechanisms
	// sb.append(SASLAuthentication.getSASLMechanisms(session));
	// Include Stream features
	String specificFeatures = session.getAvailableStreamFeatures();
	if (specificFeatures != null) {
	    sb.append(specificFeatures);
	}
	sb.append("</stream:features>");

	connection.deliverString(sb.toString());
	logger.debug("Session created " + session.getSessionID());
	connection.setSessionID(session.getSessionID());
	return session;

    }

    private static int[] decodeVersion(String value) {
	int[] answer = new int[] { 0, 0 };
	String[] versionString = value.split("\\.");
	answer[0] = Integer.parseInt(versionString[0]);
	answer[1] = Integer.parseInt(versionString[1]);
	return answer;
    }

    @Override
    public void deliverPacket(Packet packet) {
	try {
	    getConnection().deliver(packet);
	} catch (NotAllowedException e) {
	   logger_.error("Not allowed");
	}
    }

    
    
    
    
    @Override
    public boolean isInitializedPresence() {
	return initial_;
    }

    @Override
    public void setInitializedPresence(boolean isInitialized) {
	initial_ = isInitialized;
    }
/*
    @Override
    public void addSubscriber(UserID id) {
	subscribers_.add(id);
	
    }

    @Override
    public Set<UserID> getSubscribers() {
	return subscribers_;
    }

    @Override
    public void removeSubscriber(UserID id) {
	subscribers_.remove(id);
	
    }

    @Override
    public void setSubscribers(Set<UserID> set) {
	subscribers_ = set;
	
    }
*/
    @Override
    public void setRosterRequested(boolean isRequested) {
	this.rosterRequested_ = isRequested;
	
    }

    @Override
    public boolean getRosterRequested() {
	
	return this.rosterRequested_;
    }
    

}
